/*
 * Copyright (c) 2008 INESC Porto
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Gustavo Carneiro  <gjc@inescporto.pt>
 */

#include "pyviz.h"

#include "visual-simulator-impl.h"

#include "ns3/abort.h"
#include "ns3/config.h"
#include "ns3/ethernet-header.h"
#include "ns3/log.h"
#include "ns3/node-list.h"
#include "ns3/ppp-header.h"
#include "ns3/simulator.h"
#include "ns3/wifi-mac-header.h"
#include "ns3/wifi-net-device.h"

#include <cstdlib>
#include <sstream>

NS_LOG_COMPONENT_DEFINE("PyViz");

#define NUM_LAST_PACKETS 10

static std::vector<std::string>
PathSplit(std::string str)
{
    std::vector<std::string> results;
    size_t cutAt;
    while ((cutAt = str.find_first_of('/')) != std::string::npos)
    {
        if (cutAt > 0)
        {
            results.push_back(str.substr(0, cutAt));
        }
        str = str.substr(cutAt + 1);
    }
    if (!str.empty())
    {
        results.push_back(str);
    }
    return results;
}

namespace ns3
{

static PyViz* g_visualizer = nullptr; ///< the visualizer

/**
 * PyVizPacketTag structure
 */
struct PyVizPacketTag : public Tag
{
    static TypeId GetTypeId();
    TypeId GetInstanceTypeId() const override;
    uint32_t GetSerializedSize() const override;
    void Serialize(TagBuffer buf) const override;
    void Deserialize(TagBuffer buf) override;
    void Print(std::ostream& os) const override;
    PyVizPacketTag();

    uint32_t m_packetId; ///< packet id
};

/**
 * @brief Get the type ID.
 * @return the object TypeId
 */
TypeId
PyVizPacketTag::GetTypeId()
{
    static TypeId tid = TypeId("ns3::PyVizPacketTag")
                            .SetParent<Tag>()
                            .SetGroupName("Visualizer")
                            .AddConstructor<PyVizPacketTag>();
    return tid;
}

TypeId
PyVizPacketTag::GetInstanceTypeId() const
{
    return GetTypeId();
}

uint32_t
PyVizPacketTag::GetSerializedSize() const
{
    return 4;
}

void
PyVizPacketTag::Serialize(TagBuffer buf) const
{
    buf.WriteU32(m_packetId);
}

void
PyVizPacketTag::Deserialize(TagBuffer buf)
{
    m_packetId = buf.ReadU32();
}

void
PyVizPacketTag::Print(std::ostream& os) const
{
    os << "PacketId=" << m_packetId;
}

PyVizPacketTag::PyVizPacketTag()
    : Tag()
{
}

PyViz::PyViz()
{
    NS_LOG_FUNCTION_NOARGS();
    NS_ASSERT(g_visualizer == nullptr);
    g_visualizer = this;

    // WiFi
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacTx",
                            MakeCallback(&PyViz::TraceNetDevTxWifi, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacRx",
                            MakeCallback(&PyViz::TraceNetDevRxWifi, this));

    // CSMA
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacTx",
                            MakeCallback(&PyViz::TraceNetDevTxCsma, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacRx",
                            MakeCallback(&PyViz::TraceNetDevRxCsma, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacPromiscRx",
                            MakeCallback(&PyViz::TraceNetDevPromiscRxCsma, this));

    // Generic queue drop
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/TxQueue/Drop",
                            MakeCallback(&PyViz::TraceDevQueueDrop, this));
    // IPv4 drop
    Config::ConnectFailSafe("/NodeList/*/$ns3::Ipv4L3Protocol/Drop",
                            MakeCallback(&PyViz::TraceIpv4Drop, this));

    // Point-to-Point
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/MacTx",
                            MakeCallback(&PyViz::TraceNetDevTxPointToPoint, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/MacRx",
                            MakeCallback(&PyViz::TraceNetDevRxPointToPoint, this));

    // WiMax
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WimaxNetDevice/Tx",
                            MakeCallback(&PyViz::TraceNetDevTxWimax, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WimaxNetDevice/Rx",
                            MakeCallback(&PyViz::TraceNetDevRxWimax, this));

    // LTE
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::LteNetDevice/Tx",
                            MakeCallback(&PyViz::TraceNetDevTxLte, this));

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::LteNetDevice/Rx",
                            MakeCallback(&PyViz::TraceNetDevRxLte, this));
}

void
PyViz::RegisterCsmaLikeDevice(const std::string& deviceTypeName)
{
    TypeId::LookupByName(deviceTypeName); // this will assert if the type name is invalid

    std::ostringstream sstream;
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/MacTx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevTxCsma, this));

    sstream.str("");
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevRxCsma, this));

    sstream.str("");
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/PromiscRx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevPromiscRxCsma, this));
}

void
PyViz::RegisterWifiLikeDevice(const std::string& deviceTypeName)
{
    TypeId::LookupByName(deviceTypeName); // this will assert if the type name is invalid

    std::ostringstream sstream;
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Tx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevTxWifi, this));

    sstream.str("");
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevRxWifi, this));
}

void
PyViz::RegisterPointToPointLikeDevice(const std::string& deviceTypeName)
{
    TypeId::LookupByName(deviceTypeName); // this will assert if the type name is invalid

    std::ostringstream sstream;
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/TxQueue/Dequeue";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevTxPointToPoint, this));

    sstream.str("");
    sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx";
    Config::Connect(sstream.str(), MakeCallback(&PyViz::TraceNetDevRxPointToPoint, this));
}

void
PyViz::SetPacketCaptureOptions(uint32_t nodeId, PacketCaptureOptions options)
{
    NS_LOG_DEBUG("  SetPacketCaptureOptions "
                 << nodeId << " PacketCaptureOptions (headers size = " << options.headers.size()
                 << " mode = " << options.mode << " numLastPackets = " << options.numLastPackets
                 << ")");
    m_packetCaptureOptions[nodeId] = options;
}

void
PyViz::RegisterDropTracePath(const std::string& tracePath)
{
    Config::Connect(tracePath, MakeCallback(&PyViz::TraceDevQueueDrop, this));
}

PyViz::~PyViz()
{
    NS_LOG_FUNCTION_NOARGS();

    NS_ASSERT(g_visualizer == this);
    g_visualizer = nullptr;
}

void
PyViz::DoPause(const std::string& message)
{
    m_pauseMessages.push_back(message);
    m_stop = true;
    NS_LOG_LOGIC(Simulator::Now().As(Time::S)
                 << ": Have " << g_visualizer->m_pauseMessages.size() << " pause messages");
}

void
PyViz::Pause(const std::string& message)
{
    NS_ASSERT(g_visualizer);
    g_visualizer->DoPause(message);
}

std::vector<std::string>
PyViz::GetPauseMessages() const
{
    NS_LOG_LOGIC(Simulator::Now().As(Time::S)
                 << ": GetPauseMessages: have " << g_visualizer->m_pauseMessages.size()
                 << " pause messages");
    return m_pauseMessages;
}

void
PyViz::CallbackStopSimulation()
{
    NS_LOG_FUNCTION_NOARGS();
    if (m_runUntil <= Simulator::Now())
    {
        Simulator::Stop(Seconds(0)); // Stop right now
        m_stop = true;
    }
}

void
PyViz::SimulatorRunUntil(Time time)
{
    NS_LOG_LOGIC("SimulatorRunUntil " << time << " (now is " << Simulator::Now() << ")");

    m_pauseMessages.clear();
    m_transmissionSamples.clear();
    m_packetDrops.clear();

    Time expirationTime = Simulator::Now() - Seconds(10);

    // Clear very old transmission records
    for (auto iter = m_txRecords.begin(); iter != m_txRecords.end();)
    {
        if (iter->second.time < expirationTime)
        {
            m_txRecords.erase(iter++);
        }
        else
        {
            iter++;
        }
    }

    // Clear very old packets of interest
    for (auto iter = m_packetsOfInterest.begin(); iter != m_packetsOfInterest.end();)
    {
        if (iter->second < expirationTime)
        {
            m_packetsOfInterest.erase(iter++);
        }
        else
        {
            iter++;
        }
    }

    if (Simulator::Now() >= time)
    {
        return;
    }
    // Schedule a dummy callback function for the target time, to make
    // sure we stop at the right time.  Otherwise, simulations with few
    // events just appear to "jump" big chunks of time.
    NS_LOG_LOGIC("Schedule dummy callback to be called in " << (time - Simulator::Now()));
    m_runUntil = time;
    m_stop = false;
    Simulator::ScheduleWithContext(Simulator::NO_CONTEXT,
                                   time - Simulator::Now(),
                                   &PyViz::CallbackStopSimulation,
                                   this);

    Ptr<SimulatorImpl> impl = Simulator::GetImplementation();
    Ptr<VisualSimulatorImpl> visualImpl = DynamicCast<VisualSimulatorImpl>(impl);
    if (visualImpl)
    {
        visualImpl->RunRealSimulator();
    }
    else
    {
        impl->Run();
    }
}

bool
PyViz::TransmissionSampleKey::operator<(const PyViz::TransmissionSampleKey& other) const
{
    if (this->transmitter < other.transmitter)
    {
        return true;
    }
    if (this->transmitter != other.transmitter)
    {
        return false;
    }
    if (this->receiver < other.receiver)
    {
        return true;
    }
    if (this->receiver != other.receiver)
    {
        return false;
    }
    return this->channel < other.channel;
}

bool
PyViz::TransmissionSampleKey::operator==(const PyViz::TransmissionSampleKey& other) const
{
    bool retval = (transmitter == other.transmitter) && (receiver == other.receiver) &&
                  (channel == other.channel);
    return retval;
}

PyViz::NetDeviceStatistics&
PyViz::FindNetDeviceStatistics(int node, int interface)
{
    auto nodeStatsIter = m_nodesStatistics.find(node);
    std::vector<NetDeviceStatistics>* stats;
    if (nodeStatsIter == m_nodesStatistics.end())
    {
        stats = &m_nodesStatistics[node];
        stats->resize(NodeList::GetNode(node)->GetNDevices());
    }
    else
    {
        stats = &(nodeStatsIter->second);
    }
    NetDeviceStatistics& devStats = (*stats)[interface];
    return devStats;
}

bool
PyViz::GetPacketCaptureOptions(uint32_t nodeId, const PacketCaptureOptions** outOptions) const
{
    auto iter = m_packetCaptureOptions.find(nodeId);
    if (iter == m_packetCaptureOptions.end())
    {
        return false;
    }
    else
    {
        *outOptions = &iter->second;
        return true;
    }
}

bool
PyViz::FilterPacket(Ptr<const Packet> packet, const PacketCaptureOptions& options)
{
    switch (options.mode)
    {
    case PACKET_CAPTURE_DISABLED:
        return false;

    case PACKET_CAPTURE_FILTER_HEADERS_OR: {
        PacketMetadata::ItemIterator metadataIterator = packet->BeginItem();
        while (metadataIterator.HasNext())
        {
            PacketMetadata::Item item = metadataIterator.Next();
            if (options.headers.find(item.tid) != options.headers.end())
            {
                return true;
            }
        }
        return false;
    }

    case PACKET_CAPTURE_FILTER_HEADERS_AND: {
        std::set<TypeId> missingHeaders(options.headers);
        PacketMetadata::ItemIterator metadataIterator = packet->BeginItem();
        while (metadataIterator.HasNext())
        {
            PacketMetadata::Item item = metadataIterator.Next();
            auto missingIter = missingHeaders.find(item.tid);
            if (missingIter != missingHeaders.end())
            {
                missingHeaders.erase(missingIter);
            }
        }
        return missingHeaders.empty();
    }

    default:
        NS_FATAL_ERROR("should not be reached");
        return false;
    }
}

void
PyViz::TraceDevQueueDrop(std::string context, Ptr<const Packet> packet)
{
    NS_LOG_FUNCTION(context << packet->GetUid());
    std::vector<std::string> splitPath = PathSplit(context);
    int nodeIndex = std::stoi(splitPath[1]);
    Ptr<Node> node = NodeList::GetNode(nodeIndex);

    if (m_nodesOfInterest.find(nodeIndex) == m_nodesOfInterest.end())
    {
        // if the transmitting node is not "of interest", we still
        // record the transmission if it is a packet of interest.
        if (m_packetsOfInterest.find(packet->GetUid()) == m_packetsOfInterest.end())
        {
            NS_LOG_DEBUG("Packet " << packet->GetUid() << " is not of interest");
            return;
        }
    }

    // ---- "last packets"
    const PacketCaptureOptions* captureOptions;
    if (GetPacketCaptureOptions(nodeIndex, &captureOptions) &&
        FilterPacket(packet, *captureOptions))
    {
        LastPacketsSample& last = m_lastPackets[nodeIndex];
        PacketSample lastPacket;
        lastPacket.time = Simulator::Now();
        lastPacket.packet = packet->Copy();
        lastPacket.device = nullptr;
        last.lastDroppedPackets.push_back(lastPacket);
        while (last.lastDroppedPackets.size() > captureOptions->numLastPackets)
        {
            last.lastDroppedPackets.erase(last.lastDroppedPackets.begin());
        }
    }

    auto iter = m_packetDrops.find(node);
    if (iter == m_packetDrops.end())
    {
        m_packetDrops[node] = packet->GetSize();
    }
    else
    {
        iter->second += packet->GetSize();
    }
}

void
PyViz::TraceIpv4Drop(std::string context,
                     const ns3::Ipv4Header& hdr,
                     Ptr<const Packet> packet,
                     ns3::Ipv4L3Protocol::DropReason reason,
                     Ptr<Ipv4> dummy_ipv4,
                     uint32_t interface)
{
    Ptr<Packet> packetCopy = packet->Copy();
    packetCopy->AddHeader(hdr);
    TraceDevQueueDrop(context, packetCopy);
}

// --------- TX device tracing -------------------

void
PyViz::TraceNetDevTxCommon(const std::string& context,
                           Ptr<const Packet> packet,
                           const Mac48Address& destinationAddress)
{
    NS_LOG_FUNCTION(context << packet->GetUid() << *packet);

    std::vector<std::string> splitPath = PathSplit(context);
    int nodeIndex = std::stoi(splitPath[1]);
    int devIndex = std::stoi(splitPath[3]);
    Ptr<Node> node = NodeList::GetNode(nodeIndex);
    Ptr<NetDevice> device = node->GetDevice(devIndex);

    // ---- statistics
    NetDeviceStatistics& stats = FindNetDeviceStatistics(nodeIndex, devIndex);
    ++stats.transmittedPackets;
    stats.transmittedBytes += packet->GetSize();

    // ---- "last packets"
    const PacketCaptureOptions* captureOptions;
    if (GetPacketCaptureOptions(nodeIndex, &captureOptions) &&
        FilterPacket(packet, *captureOptions))
    {
        LastPacketsSample& last = m_lastPackets[nodeIndex];
        TxPacketSample lastPacket;
        lastPacket.time = Simulator::Now();
        lastPacket.packet = packet->Copy();
        lastPacket.device = device;
        lastPacket.to = destinationAddress;
        last.lastTransmittedPackets.push_back(lastPacket);
        while (last.lastTransmittedPackets.size() > captureOptions->numLastPackets)
        {
            last.lastTransmittedPackets.erase(last.lastTransmittedPackets.begin());
        }
    }

    // ---- transmissions records

    if (m_nodesOfInterest.find(nodeIndex) == m_nodesOfInterest.end())
    {
        // if the transmitting node is not "of interest", we still
        // record the transmission if it is a packet of interest.
        if (m_packetsOfInterest.find(packet->GetUid()) == m_packetsOfInterest.end())
        {
            NS_LOG_DEBUG("Packet " << packet->GetUid() << " is not of interest");
            return;
        }
    }
    else
    {
        // We will follow this packet throughout the network.
        m_packetsOfInterest[packet->GetUid()] = Simulator::Now();
    }

    TxRecordValue record = {Simulator::Now(), node, false};
    if (destinationAddress == device->GetBroadcast())
    {
        record.isBroadcast = true;
    }

    m_txRecords[TxRecordKey(device->GetChannel(), packet->GetUid())] = record;

    PyVizPacketTag tag;
    // packet->RemovePacketTag (tag);
    tag.m_packetId = packet->GetUid();
    packet->AddByteTag(tag);
}

void
PyViz::TraceNetDevTxWifi(std::string context, Ptr<const Packet> packet)
{
    NS_LOG_FUNCTION(context << packet->GetUid() << *packet);

    /*
     *  To DS    From DS   Address 1    Address 2    Address 3    Address 4
     *----------------------------------------------------------------------
     *    0        0       Destination  Source        BSSID         N/A
     *    0        1       Destination  BSSID         Source        N/A
     *    1        0       BSSID        Source        Destination   N/A
     *    1        1       Receiver     Transmitter   Destination   Source
     */
    WifiMacHeader hdr;
    NS_ABORT_IF(packet->PeekHeader(hdr) == 0);
    Mac48Address destinationAddress;
    if (hdr.IsToDs())
    {
        destinationAddress = hdr.GetAddr3();
    }
    else
    {
        destinationAddress = hdr.GetAddr1();
    }
    TraceNetDevTxCommon(context, packet, destinationAddress);
}

void
PyViz::TraceNetDevTxCsma(std::string context, Ptr<const Packet> packet)
{
    EthernetHeader ethernetHeader;
    NS_ABORT_IF(packet->PeekHeader(ethernetHeader) == 0);
    TraceNetDevTxCommon(context, packet, ethernetHeader.GetDestination());
}

void
PyViz::TraceNetDevTxPointToPoint(std::string context, Ptr<const Packet> packet)
{
    TraceNetDevTxCommon(context, packet, Mac48Address());
}

// --------- RX device tracing -------------------

void
PyViz::TraceNetDevRxCommon(const std::string& context,
                           Ptr<const Packet> packet,
                           const Mac48Address& from)
{
    uint32_t uid;
    PyVizPacketTag tag;
    if (packet->FindFirstMatchingByteTag(tag))
    {
        uid = tag.m_packetId;
    }
    else
    {
        // NS_ASSERT (0);
        NS_LOG_WARN("Packet has no byte tag; wimax link?");
        uid = packet->GetUid();
    }

    NS_LOG_FUNCTION(context << uid);
    std::vector<std::string> splitPath = PathSplit(context);
    int nodeIndex = std::stoi(splitPath[1]);
    int devIndex = std::stoi(splitPath[3]);

    // ---- statistics
    NetDeviceStatistics& stats = FindNetDeviceStatistics(nodeIndex, devIndex);
    ++stats.receivedPackets;
    stats.receivedBytes += packet->GetSize();

    Ptr<Node> node = NodeList::GetNode(nodeIndex);
    Ptr<NetDevice> device = node->GetDevice(devIndex);

    // ---- "last packets"
    const PacketCaptureOptions* captureOptions;
    if (GetPacketCaptureOptions(nodeIndex, &captureOptions) &&
        FilterPacket(packet, *captureOptions))
    {
        LastPacketsSample& last = m_lastPackets[nodeIndex];
        RxPacketSample lastPacket;
        lastPacket.time = Simulator::Now();
        lastPacket.packet = packet->Copy();
        lastPacket.device = device;
        lastPacket.from = from;
        last.lastReceivedPackets.push_back(lastPacket);
        while (last.lastReceivedPackets.size() > captureOptions->numLastPackets)
        {
            last.lastReceivedPackets.erase(last.lastReceivedPackets.begin());
        }
    }

    // ---- transmissions
    if (m_packetsOfInterest.find(uid) == m_packetsOfInterest.end())
    {
        NS_LOG_DEBUG("RX Packet " << uid << " is not of interest");
        return;
    }

    Ptr<Channel> channel = device->GetChannel();

    auto recordIter = m_txRecords.find(TxRecordKey(channel, uid));

    if (recordIter == m_txRecords.end())
    {
        NS_LOG_DEBUG("RX Packet " << uid << " was not transmitted?!");
        return;
    }

    TxRecordValue& record = recordIter->second;

    if (record.srcNode == node)
    {
        NS_LOG_WARN("Node " << node->GetId() << " receiving back the same packet (UID=" << uid
                            << ") it had previously transmitted, on the same channel!");
        return;
    }

    TransmissionSampleKey key = {record.srcNode, node, channel};

#ifdef NS3_LOG_ENABLE
    NS_LOG_DEBUG("m_transmissionSamples begin:");
    if (g_log.IsEnabled(ns3::LOG_DEBUG))
    {
        for (auto iter = m_transmissionSamples.begin(); iter != m_transmissionSamples.end(); iter++)
        {
            NS_LOG_DEBUG(iter->first.transmitter
                         << "/" << iter->first.transmitter->GetId() << ", " << iter->first.receiver
                         << "/" << iter->first.receiver->GetId() << ", " << iter->first.channel
                         << " => " << iter->second.bytes << " (@ " << &iter->second << ")");
        }
    }
    NS_LOG_DEBUG("m_transmissionSamples end.");
#endif

    auto iter = m_transmissionSamples.find(key);

    if (iter == m_transmissionSamples.end())
    {
        TransmissionSampleValue sample = {packet->GetSize()};
        NS_LOG_DEBUG("RX: from " << key.transmitter << "/" << key.transmitter->GetId() << " to "
                                 << key.receiver << "/" << key.receiver->GetId() << " channel "
                                 << channel << ": " << packet->GetSize()
                                 << " bytes more. => new sample with " << packet->GetSize()
                                 << " bytes.");
        m_transmissionSamples[key] = sample;
    }
    else
    {
        TransmissionSampleValue& sample = iter->second;
        NS_LOG_DEBUG("RX: from " << key.transmitter << "/" << key.transmitter->GetId() << " to "
                                 << key.receiver << "/" << key.receiver->GetId() << " channel "
                                 << channel << ": " << packet->GetSize()
                                 << " bytes more. => sample " << &sample << " with bytes "
                                 << sample.bytes);

        sample.bytes += packet->GetSize();
    }
}

void
PyViz::TraceNetDevRxWifi(std::string context, Ptr<const Packet> packet)
{
    NS_LOG_FUNCTION(context << packet->GetUid());

    /*
     *  To DS    From DS   Address 1    Address 2    Address 3    Address 4
     *----------------------------------------------------------------------
     *    0        0       Destination  Source        BSSID         N/A
     *    0        1       Destination  BSSID         Source        N/A
     *    1        0       BSSID        Source        Destination   N/A
     *    1        1       Receiver     Transmitter   Destination   Source
     */
    WifiMacHeader hdr;
    NS_ABORT_IF(packet->PeekHeader(hdr) == 0);
    Mac48Address sourceAddress;
    if (!hdr.IsFromDs())
    {
        sourceAddress = hdr.GetAddr2();
    }
    else if (!hdr.IsToDs())
    {
        sourceAddress = hdr.GetAddr3();
    }
    else // if (hdr.IsToDs())
    {
        sourceAddress = hdr.GetAddr4();
    }

    TraceNetDevRxCommon(context, packet, sourceAddress);
}

void
PyViz::TraceNetDevRxCsma(std::string context, Ptr<const Packet> packet)
{
    EthernetHeader ethernetHeader;
    NS_ABORT_IF(packet->PeekHeader(ethernetHeader) == 0);
    TraceNetDevRxCommon(context, packet, ethernetHeader.GetSource());
}

void
PyViz::TraceNetDevRxPointToPoint(std::string context, Ptr<const Packet> packet)
{
    TraceNetDevRxCommon(context, packet, Mac48Address());
}

void
PyViz::TraceNetDevPromiscRxCsma(std::string context, Ptr<const Packet> packet)
{
    EthernetHeader ethernetHeader;
    NS_ABORT_IF(packet->PeekHeader(ethernetHeader) == 0);

    NetDevice::PacketType packetType = NetDevice::PACKET_OTHERHOST; // FIXME

    // Other packet types are already being received by
    // TraceNetDevRxCsma; we don't want to receive them twice.
    if (packetType == NetDevice::PACKET_OTHERHOST)
    {
        TraceNetDevRxCommon(context, packet, ethernetHeader.GetDestination());
    }
}

void
PyViz::TraceNetDevTxWimax(std::string context,
                          Ptr<const Packet> packet,
                          const Mac48Address& destination)
{
    NS_LOG_FUNCTION(context);
    TraceNetDevTxCommon(context, packet, destination);
}

void
PyViz::TraceNetDevRxWimax(std::string context, Ptr<const Packet> packet, const Mac48Address& source)
{
    NS_LOG_FUNCTION(context);
    TraceNetDevRxCommon(context, packet, source);
}

void
PyViz::TraceNetDevTxLte(std::string context,
                        Ptr<const Packet> packet,
                        const Mac48Address& destination)
{
    NS_LOG_FUNCTION(context);
    TraceNetDevTxCommon(context, packet, destination);
}

void
PyViz::TraceNetDevRxLte(std::string context, Ptr<const Packet> packet, const Mac48Address& source)
{
    NS_LOG_FUNCTION(context);
    TraceNetDevRxCommon(context, packet, source);
}

// ---------------------

PyViz::TransmissionSampleList
PyViz::GetTransmissionSamples() const
{
    NS_LOG_DEBUG("GetTransmissionSamples BEGIN");
    TransmissionSampleList list;
    for (auto iter = m_transmissionSamples.begin(); iter != m_transmissionSamples.end(); iter++)
    {
        TransmissionSample sample;
        sample.transmitter = iter->first.transmitter;
        sample.receiver = iter->first.receiver;
        sample.channel = iter->first.channel;
        sample.bytes = iter->second.bytes;
        NS_LOG_DEBUG("from " << sample.transmitter->GetId() << " to " << sample.receiver->GetId()
                             << ": " << sample.bytes << " bytes.");
        list.push_back(sample);
    }
    NS_LOG_DEBUG("GetTransmissionSamples END");
    return list;
}

PyViz::PacketDropSampleList
PyViz::GetPacketDropSamples() const
{
    NS_LOG_DEBUG("GetPacketDropSamples BEGIN");
    PacketDropSampleList list;
    for (auto iter = m_packetDrops.begin(); iter != m_packetDrops.end(); iter++)
    {
        PacketDropSample sample;
        sample.transmitter = iter->first;
        sample.bytes = iter->second;
        NS_LOG_DEBUG("in " << sample.transmitter->GetId() << ": " << sample.bytes
                           << " bytes dropped.");
        list.push_back(sample);
    }
    NS_LOG_DEBUG("GetPacketDropSamples END");
    return list;
}

void
PyViz::SetNodesOfInterest(std::set<uint32_t> nodes)
{
    m_nodesOfInterest = nodes;
}

std::vector<PyViz::NodeStatistics>
PyViz::GetNodesStatistics() const
{
    std::vector<PyViz::NodeStatistics> retval;
    for (auto iter = m_nodesStatistics.begin(); iter != m_nodesStatistics.end(); iter++)
    {
        NodeStatistics stats = {iter->first, iter->second};
        retval.push_back(stats);
    }
    return retval;
}

PyViz::LastPacketsSample
PyViz::GetLastPackets(uint32_t nodeId) const
{
    NS_LOG_DEBUG("GetLastPackets: " << nodeId);

    auto iter = m_lastPackets.find(nodeId);
    if (iter != m_lastPackets.end())
    {
        return iter->second;
    }
    else
    {
        return LastPacketsSample();
    }
}

namespace
{
/// Adapted from http://en.wikipedia.org/w/index.php?title=Line_clipping&oldid=248609574
class FastClipping
{
  public:
    /// Vector2 structure
    struct Vector2
    {
        double x; ///< X
        double y; ///< Y
    };

    Vector2 m_clipMin; ///< clip minimum
    Vector2 m_clipMax; ///< clip maximum

    /// Line structure
    struct Line
    {
        Vector2 start; ///< start
        Vector2 end;   ///<  end
        double dx;     ///< dX
        double dy;     ///< dY
    };

  private:
    /**
     * Clip start top function
     * @param line the clip line
     */
    void ClipStartTop(Line& line) const
    {
        line.start.x += line.dx * (m_clipMin.y - line.start.y) / line.dy;
        line.start.y = m_clipMin.y;
    }

    /**
     * Clip start bottom function
     * @param line the clip line
     */
    void ClipStartBottom(Line& line) const
    {
        line.start.x += line.dx * (m_clipMax.y - line.start.y) / line.dy;
        line.start.y = m_clipMax.y;
    }

    /**
     * Clip start right function
     * @param line the clip line
     */
    void ClipStartRight(Line& line) const
    {
        line.start.y += line.dy * (m_clipMax.x - line.start.x) / line.dx;
        line.start.x = m_clipMax.x;
    }

    /**
     * Clip start left function
     * @param line the clip line
     */
    void ClipStartLeft(Line& line) const
    {
        line.start.y += line.dy * (m_clipMin.x - line.start.x) / line.dx;
        line.start.x = m_clipMin.x;
    }

    /**
     * Clip end top function
     * @param line the clip line
     */
    void ClipEndTop(Line& line) const
    {
        line.end.x += line.dx * (m_clipMin.y - line.end.y) / line.dy;
        line.end.y = m_clipMin.y;
    }

    /**
     * Clip end bottom function
     * @param line the clip line
     */
    void ClipEndBottom(Line& line) const
    {
        line.end.x += line.dx * (m_clipMax.y - line.end.y) / line.dy;
        line.end.y = m_clipMax.y;
    }

    /**
     * Clip end right function
     * @param line the clip line
     */
    void ClipEndRight(Line& line) const
    {
        line.end.y += line.dy * (m_clipMax.x - line.end.x) / line.dx;
        line.end.x = m_clipMax.x;
    }

    /**
     * Clip end left function
     * @param line the clip line
     */
    void ClipEndLeft(Line& line) const
    {
        line.end.y += line.dy * (m_clipMin.x - line.end.x) / line.dx;
        line.end.x = m_clipMin.x;
    }

  public:
    /**
     * Constructor
     *
     * @param clipMin minimum clipping vector
     * @param clipMax maximum clipping vector
     */
    FastClipping(Vector2 clipMin, Vector2 clipMax)
        : m_clipMin(clipMin),
          m_clipMax(clipMax)
    {
    }

    /**
     * Clip line function
     * @param line the clip line
     * @returns true if clipped
     */
    bool ClipLine(Line& line)
    {
        uint8_t lineCode = 0;

        if (line.end.y < m_clipMin.y)
        {
            lineCode |= 8;
        }
        else if (line.end.y > m_clipMax.y)
        {
            lineCode |= 4;
        }

        if (line.end.x > m_clipMax.x)
        {
            lineCode |= 2;
        }
        else if (line.end.x < m_clipMin.x)
        {
            lineCode |= 1;
        }

        if (line.start.y < m_clipMin.y)
        {
            lineCode |= 128;
        }
        else if (line.start.y > m_clipMax.y)
        {
            lineCode |= 64;
        }

        if (line.start.x > m_clipMax.x)
        {
            lineCode |= 32;
        }
        else if (line.start.x < m_clipMin.x)
        {
            lineCode |= 16;
        }

        // 9 - 8 - A
        // |   |   |
        // 1 - 0 - 2
        // |   |   |
        // 5 - 4 - 6
        switch (lineCode)
        {
        // center
        case 0x00:
            return true;

        case 0x01:
            ClipEndLeft(line);
            return true;

        case 0x02:
            ClipEndRight(line);
            return true;

        case 0x04:
            ClipEndBottom(line);
            return true;

        case 0x05:
            ClipEndLeft(line);
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        case 0x06:
            ClipEndRight(line);
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        case 0x08:
            ClipEndTop(line);
            return true;

        case 0x09:
            ClipEndLeft(line);
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            return true;

        case 0x0A:
            ClipEndRight(line);
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            return true;

        // left
        case 0x10:
            ClipStartLeft(line);
            return true;

        case 0x12:
            ClipStartLeft(line);
            ClipEndRight(line);
            return true;

        case 0x14:
            ClipStartLeft(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            ClipEndBottom(line);
            return true;

        case 0x16:
            ClipStartLeft(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            ClipEndBottom(line);
            if (line.end.x > m_clipMax.x)
            {
                ClipEndRight(line);
            }
            return true;

        case 0x18:
            ClipStartLeft(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            ClipEndTop(line);
            return true;

        case 0x1A:
            ClipStartLeft(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            ClipEndTop(line);
            if (line.end.x > m_clipMax.x)
            {
                ClipEndRight(line);
            }
            return true;

        // right
        case 0x20:
            ClipStartRight(line);
            return true;

        case 0x21:
            ClipStartRight(line);
            ClipEndLeft(line);
            return true;

        case 0x24:
            ClipStartRight(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            ClipEndBottom(line);
            return true;

        case 0x25:
            ClipStartRight(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            ClipEndBottom(line);
            if (line.end.x < m_clipMin.x)
            {
                ClipEndLeft(line);
            }
            return true;

        case 0x28:
            ClipStartRight(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            ClipEndTop(line);
            return true;

        case 0x29:
            ClipStartRight(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            ClipEndTop(line);
            if (line.end.x < m_clipMin.x)
            {
                ClipEndLeft(line);
            }
            return true;

        // bottom
        case 0x40:
            ClipStartBottom(line);
            return true;

        case 0x41:
            ClipStartBottom(line);
            if (line.start.x < m_clipMin.x)
            {
                return false;
            }
            ClipEndLeft(line);
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        case 0x42:
            ClipStartBottom(line);
            if (line.start.x > m_clipMax.x)
            {
                return false;
            }
            ClipEndRight(line);
            return true;

        case 0x48:
            ClipStartBottom(line);
            ClipEndTop(line);
            return true;

        case 0x49:
            ClipStartBottom(line);
            if (line.start.x < m_clipMin.x)
            {
                return false;
            }
            ClipEndLeft(line);
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            return true;

        case 0x4A:
            ClipStartBottom(line);
            if (line.start.x > m_clipMax.x)
            {
                return false;
            }
            ClipEndRight(line);
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            return true;

        // bottom-left
        case 0x50:
            ClipStartLeft(line);
            if (line.start.y > m_clipMax.y)
            {
                ClipStartBottom(line);
            }
            return true;

        case 0x52:
            ClipEndRight(line);
            if (line.end.y > m_clipMax.y)
            {
                return false;
            }
            ClipStartBottom(line);
            if (line.start.x < m_clipMin.x)
            {
                ClipStartLeft(line);
            }
            return true;

        case 0x58:
            ClipEndTop(line);
            if (line.end.x < m_clipMin.x)
            {
                return false;
            }
            ClipStartBottom(line);
            if (line.start.x < m_clipMin.x)
            {
                ClipStartLeft(line);
            }
            return true;

        case 0x5A:
            ClipStartLeft(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            ClipEndRight(line);
            if (line.end.y > m_clipMax.y)
            {
                return false;
            }
            if (line.start.y > m_clipMax.y)
            {
                ClipStartBottom(line);
            }
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            return true;

        // bottom-right
        case 0x60:
            ClipStartRight(line);
            if (line.start.y > m_clipMax.y)
            {
                ClipStartBottom(line);
            }
            return true;

        case 0x61:
            ClipEndLeft(line);
            if (line.end.y > m_clipMax.y)
            {
                return false;
            }
            ClipStartBottom(line);
            if (line.start.x > m_clipMax.x)
            {
                ClipStartRight(line);
            }
            return true;

        case 0x68:
            ClipEndTop(line);
            if (line.end.x > m_clipMax.x)
            {
                return false;
            }
            ClipStartRight(line);
            if (line.start.y > m_clipMax.y)
            {
                ClipStartBottom(line);
            }
            return true;

        case 0x69:
            ClipEndLeft(line);
            if (line.end.y > m_clipMax.y)
            {
                return false;
            }
            ClipStartRight(line);
            if (line.start.y < m_clipMin.y)
            {
                return false;
            }
            if (line.end.y < m_clipMin.y)
            {
                ClipEndTop(line);
            }
            if (line.start.y > m_clipMax.y)
            {
                ClipStartBottom(line);
            }
            return true;

        // top
        case 0x80:
            ClipStartTop(line);
            return true;

        case 0x81:
            ClipStartTop(line);
            if (line.start.x < m_clipMin.x)
            {
                return false;
            }
            ClipEndLeft(line);
            return true;

        case 0x82:
            ClipStartTop(line);
            if (line.start.x > m_clipMax.x)
            {
                return false;
            }
            ClipEndRight(line);
            return true;

        case 0x84:
            ClipStartTop(line);
            ClipEndBottom(line);
            return true;

        case 0x85:
            ClipStartTop(line);
            if (line.start.x < m_clipMin.x)
            {
                return false;
            }
            ClipEndLeft(line);
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        case 0x86:
            ClipStartTop(line);
            if (line.start.x > m_clipMax.x)
            {
                return false;
            }
            ClipEndRight(line);
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        // top-left
        case 0x90:
            ClipStartLeft(line);
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            return true;

        case 0x92:
            ClipEndRight(line);
            if (line.end.y < m_clipMin.y)
            {
                return false;
            }
            ClipStartTop(line);
            if (line.start.x < m_clipMin.x)
            {
                ClipStartLeft(line);
            }
            return true;

        case 0x94:
            ClipEndBottom(line);
            if (line.end.x < m_clipMin.x)
            {
                return false;
            }
            ClipStartLeft(line);
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            return true;

        case 0x96:
            ClipStartLeft(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            ClipEndRight(line);
            if (line.end.y < m_clipMin.y)
            {
                return false;
            }
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            return true;

        // top-right
        case 0xA0:
            ClipStartRight(line);
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            return true;

        case 0xA1:
            ClipEndLeft(line);
            if (line.end.y < m_clipMin.y)
            {
                return false;
            }
            ClipStartTop(line);
            if (line.start.x > m_clipMax.x)
            {
                ClipStartRight(line);
            }
            return true;

        case 0xA4:
            ClipEndBottom(line);
            if (line.end.x > m_clipMax.x)
            {
                return false;
            }
            ClipStartRight(line);
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            return true;

        case 0xA5:
            ClipEndLeft(line);
            if (line.end.y < m_clipMin.y)
            {
                return false;
            }
            ClipStartRight(line);
            if (line.start.y > m_clipMax.y)
            {
                return false;
            }
            if (line.end.y > m_clipMax.y)
            {
                ClipEndBottom(line);
            }
            if (line.start.y < m_clipMin.y)
            {
                ClipStartTop(line);
            }
            return true;
        }

        return false;
    }
};
} // namespace

void
PyViz::LineClipping(double boundsX1,
                    double boundsY1,
                    double boundsX2,
                    double boundsY2,
                    double& lineX1,
                    double& lineY1,
                    double& lineX2,
                    double& lineY2)
{
    FastClipping::Vector2 clipMin = {boundsX1, boundsY1};
    FastClipping::Vector2 clipMax = {boundsX2, boundsY2};
    FastClipping::Line line = {{lineX1, lineY1},
                               {lineX2, lineY2},
                               (lineX2 - lineX1),
                               (lineY2 - lineY1)};

    FastClipping clipper(clipMin, clipMax);
    clipper.ClipLine(line);
    lineX1 = line.start.x;
    lineX2 = line.end.x;
    lineY1 = line.start.y;
    lineY2 = line.end.y;
}

} // namespace ns3
